/**
 * Created by Administrator on 2016/3/12.
 */

if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        if (typeof this !== "function") {
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }
        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function () {
            },
            fBound = function () {
                return fToBind.apply(this instanceof fNOP && oThis
                        ? this
                        : oThis,
                    aArgs.concat(Array.prototype.slice.call(arguments)));
            };
        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
    };
}

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery'], factory);
    } else {
        factory(root.jQuery);
    }
}(this, function ($) {
    // PRIVATE
    var options = {
            seconds: 0,									// default seconds value to start timer from
            editable: false,							// this will let users make changes to the time
            restart: false,								// this will enable stop or continue after a timer callback
            duration: null,								// duration to run callback after
            // callback to run after elapsed duration
            callback: function () {
                alert('Time up!');
            },
            startTimer: function () {
            },
            pauseTimer: function () {
            },
            resumeTimer: function () {
            },
            resetTimer: function () {
            },
            removeTimer: function () {
            },
            repeat: false,								// this will repeat callback every n times duration is elapsed
            countdown: false,							// if true, this will render the timer as a countdown if duration > 0
            format: null,								// this sets the format in which the time will be printed
            updateFrequency: 1000,						// How often should timer display update (default 500ms)
            state: 'running'
        },
        display = 'html',
        TIMER_STOPPED = 'stopped',
        TIMER_RUNNING = 'running',
        TIMER_PAUSED = 'paused';

    function startTimerInterval(timer) {
        var element = timer.element;
        $(element).data('intr', setInterval(incrementSeconds.bind(timer), timer.options.updateFrequency));
        $(element).data('isTimerRunning', true);
    }

    function stopTimerInterval(timer) {
        clearInterval($(timer.element).data('intr'));
        $(timer.element).data('isTimerRunning', false);
    }

    function incrementSeconds() {
        $(this.element).data('totalSeconds', getUnixSeconds() - $(this.element).data('startTime'));
        render(this);

        // Check if totalSeconds is equal to duration if any
        if ($(this.element).data('duration') &&
            $(this.element).data('totalSeconds') % $(this.element).data('duration') === 0) {

            // If 'repeat' is not requested then disable the duration
            if (!this.options.repeat) {
                $(this.element).data('duration', null);
                this.options.duration = null;
            }

            // If this is a countdown, then end it as duration has completed
            if (this.options.countdown) {
                stopTimerInterval(this);
                this.options.countdown = false;
                $(this.element).data('state', TIMER_STOPPED);
            }

            // Run the default callback
            this.options.callback();
        }
    }

    function render(timer) {
        var element = timer.element,
            sec = $(element).data('totalSeconds');

        if (timer.options.countdown && ($(element).data('duration') > 0)) {
            sec = $(element).data('duration') - $(element).data('totalSeconds');
        }

        $(element)[display](secondsToTime(sec, timer));
        $(element).data('seconds', sec);
    }

    function makeEditable(timer) {
        var element = timer.element;
        $(element).on('focus', function () {
            pauseTimer(timer);
        });

        $(element).on('blur', function () {
            // eg. 12 sec 3:34 min 12:30 min
            var val = $(element)[display](), valArr;

            if (val.indexOf('sec') > 0) {
                // sec
                $(element).data('totalSeconds', Number(val.replace(/\ssec/g, '')));
            } else if (val.indexOf('min') > 0) {
                // min
                val = val.replace(/\smin/g, '');
                valArr = val.split(':');
                $(element).data('totalSeconds', Number(valArr[0] * 60) + Number(valArr[1]));
            } else if (val.match(/\d{1,2}:\d{2}:\d{2}/)) {
                // hrs
                valArr = val.split(':');
                $(element).data('totalSeconds', Number(valArr[0] * 3600) + Number(valArr[1] * 60) + Number(valArr[2]));
            }

            resumeTimer(timer);
        });
    }

    function getUnixSeconds() {
        return Math.round(new Date().getTime() / 1000);
    }

    function sec2TimeObj(sec) {
        var hours = 0, minutes = Math.floor(sec / 60), seconds;

        // Hours
        if (sec >= 3600) {
            hours = Math.floor(sec / 3600);
        }

        // Minutes
        if (sec >= 3600) {
            minutes = Math.floor(sec % 3600 / 60);
        }
        // Prepend 0 to minutes under 10
        if (minutes < 10 && hours > 0) {
            minutes = '0' + minutes;
        }
        // Seconds
        seconds = sec % 60;
        // Prepend 0 to seconds under 10
        if (seconds < 10 && (minutes > 0 || hours > 0)) {
            seconds = '0' + seconds;
        }

        return {
            hours: hours,
            minutes: minutes,
            seconds: seconds
        };
    }

    function secondsToTime(sec, timer) {
        var time = '',
            timeObj = sec2TimeObj(sec);

        if (timer.options.format) {
            var formatDef = [
                {identifier: '%h', value: timeObj.hours, pad: false},
                {identifier: '%m', value: timeObj.minutes, pad: false},
                {identifier: '%s', value: timeObj.seconds, pad: false},
                {identifier: '%H', value: parseInt(timeObj.hours), pad: true},
                {identifier: '%M', value: parseInt(timeObj.minutes), pad: true},
                {identifier: '%S', value: parseInt(timeObj.seconds), pad: true}
            ];
            time = timer.options.format;

            formatDef.forEach(function (format) {
                time = time.replace(
                    new RegExp(format.identifier.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'), 'g'),
                    (format.pad) ? ((format.value < 10) ? '0' + format.value : format.value) : format.value
                );
            });
        } else {
            if (timeObj.hours) {
                time = timeObj.hours + ':' + timeObj.minutes + ':' + timeObj.seconds;
            } else {
                if (timeObj.minutes) {
                    time = timeObj.minutes + ':' + timeObj.seconds + ' min';
                } else {
                    time = timeObj.seconds + ' sec';
                }
            }
        }
        return time;
    }

    function timeToSeconds(time) {
        // In case the passed arg is a number, then use that as number of seconds
        if (!isNaN(Number(time))) {
            return time;
        }

        var hMatch = time.match(/\d{1,2}h/),
            mMatch = time.match(/\d{1,2}m/),
            sMatch = time.match(/\d{1,2}s/),
            seconds = 0;

        time = time.toLowerCase();

        if (hMatch) {
            seconds += Number(hMatch[0].replace('h', '')) * 3600;
        }

        if (mMatch) {
            seconds += Number(mMatch[0].replace('m', '')) * 60;
        }

        if (sMatch) {
            seconds += Number(sMatch[0].replace('s', ''));
        }

        return seconds;
    }

    function startTimer(timer) {
        var element = timer.element;
        if (!$(element).data('isTimerRunning')) {
            render(timer);
            startTimerInterval(timer);
            $(element).data('state', TIMER_RUNNING);
            timer.options.startTimer.bind(timer).call();
        }
    }

    function pauseTimer(timer) {
        var element = timer.element;
        if ($(element).data('isTimerRunning')) {
            stopTimerInterval(timer);
            $(element).data('state', TIMER_PAUSED);
            timer.options.pauseTimer.bind(timer).call();
        }
    }

    function resumeTimer(timer) {
        var element = timer.element;
        if (!$(element).data('isTimerRunning')) {
            $(element).data('startTime', getUnixSeconds() - $(element).data('totalSeconds'));
            startTimerInterval(timer);
            $(element).data('state', TIMER_RUNNING);
            timer.options.resumeTimer.bind(timer).call();
        }
    }

    function resetTimer(timer) {
        var element = timer.element;
        $(element).data('startTime', 0);
        $(element).data('totalSeconds', 0);
        $(element).data('seconds', 0);
        $(element).data('state', TIMER_STOPPED);
        $(element).data('duration', timer.options.duration);
        timer.options.resetTimer.bind(timer).call();
    }

    function removeTimer(timer) {
        var element = timer.element;
        stopTimerInterval(timer);
        timer.options.removeTimer.bind(timer).call();
        $(element).data('plugin_' + pluginName, null);
        $(element).data('seconds', null);
        $(element).data('state', null);
        $(element)[display]('');
    }

    var Timer = function (element, userOptions) {
        var elementType;

        this.options = options = $.extend(this.options, options, userOptions);
        this.element = element;

        $(element).data('totalSeconds', options.seconds);

        $(element).data('startTime', getUnixSeconds() - $(element).data('totalSeconds'));

        $(element).data('seconds', $(element).data('totalSeconds'));
        $(element).data('state', TIMER_STOPPED);

        elementType = $(element).prop('tagName').toLowerCase();
        if (elementType === 'input' || elementType === 'textarea') {
            display = 'val';
        }

        if (this.options.duration) {
            $(element).data('duration', timeToSeconds(this.options.duration));
            this.options.duration = timeToSeconds(this.options.duration);
        }

        if (this.options.editable) {
            makeEditable(this);
        }

    };

    Timer.prototype = {
        start: function () {
            startTimer(this);
        },

        pause: function () {
            pauseTimer(this);
        },

        resume: function () {
            resumeTimer(this);
        },

        reset: function () {
            resetTimer(this);
        },

        remove: function () {
            removeTimer(this);
        }
    };

    var pluginName = 'timer';
    $.fn[pluginName] = function (options) {
        options = options || 'start';

        return this.each(function () {

            if (!($.data(this, 'plugin_' + pluginName) instanceof Timer)) {

                $.data(this, 'plugin_' + pluginName, new Timer(this, options));

            }

            var instance = $.data(this, 'plugin_' + pluginName);

            if (typeof options === 'string') {
                if (typeof instance[options] === 'function') {
                    instance[options].call(instance);
                }
            }

            if (typeof options === 'object') {
                if (instance.options.state === TIMER_RUNNING) {
                    instance.start.call(instance);
                } else {
                    render(instance);
                }
            }
        });
    };

}));
